<template>
    <div class="content pd24">
        <el-card style="width: 480px">
            <template #header>
                <div class="card-header">
                    <span>基础类型</span>
                </div>
            </template>
            <h3>数字: {{ baseNum }}</h3>
            <h3>字符串: {{ baseStr }}</h3>
            <h3>布尔值: {{ baseBoolean ? '真' : '假' }}</h3>
            <ul>
                <li v-for="item in baseArr">{{ item }}</li>
            </ul>
            <template #footer>Footer</template>
        </el-card>
        <el-card style="width: 480px">
            <template #header>
                <div class="card-header">
                    <span>函数</span>
                </div>
            </template>
            <h1>当前数字:{{ addnum }}</h1>
            <el-button type="info" @click="addNum">数值加1</el-button>
            <template #footer>Footer</template>
        </el-card>
    </div>
</template>

<script setup lang="ts">
import { ElCard } from 'element-plus';

/**
 * 基础类型
 */
let baseNum: number = 0
let baseStr: string = "123"
let baseBoolean: boolean = false
let baseArr: number[] = [1, 2, 3, 4, 5]
let baseArr2: Array<number> = [1, 2, 3, 4, 5]
baseArr2 = [5, 4, 3, 2, 1]
// 元组Tuple
let x: [string, number]
x = ["1", 2]
// x = [1,"2"] // 报错
// any 如果不希望类型检查器对值进行检查,直接通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量
let a: any = 2
a = false
// void 声明一个void类型的变量没有什么大用，因为你只能为它赋予undefined和null
// undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 null。 和 void 相似，它们的本身的类型用处不是很大
// never 类型表示的是那些永不存在的值的类型

/**
 * 类型断言
 */
// TypeScript 提供了“类型断言”这样一种手段，允许开发者在代码中“断言”某个值的类型，告诉编译器此处的值是什么类型。TypeScript 一旦发现存在类型断言，就不再对该值进行类型推断，而是直接采用断言给出的类型。
// 这种做法的实质是，允许开发者在某个位置“绕过”编译器的类型推断，让本来通不过类型检查的代码能够通过，避免编译器报错。这样虽然削弱了 TypeScript 类型系统的严格性，但是为开发者带来了方便，毕竟开发者比编译器更了解自己的代码。
// 语法一：<类型>值  <Type>value

// 语法二：值 as 类型 value as Type 推荐使用语法二
const p1: { x: number } = { x: 1, y: 1 } as { x: number }
const p: { x: number } = { x: 1, y: 1 } as { x: number, y: number }
// 类型断言要求实际的类型与断言的类型兼容，实际类型可以断言为一个更加宽泛的类型（父类型），也可以断言为一个更加精确的类型（子类型），但不能断言为一个完全无关的类型。
// 如果真的要断言成一个完全无关的类型，也是可以做到的。那就是连续进行两次类型断言，先断言成 unknown 类型或 any 类型，然后再断言为目标类型。因为any类型和unknown类型是所有其他类型的父类型，所以可以作为两种完全无关的类型的中介。
const n = 1;
const m: string = n as any as string; // 正确

/**
 * 函数
 */
let addnum = ref(0)
const addNum = (): void => {
    addnum.value++
}
const goHome = (time: number): void => {
    console.log(`还有${time}个小时下班!`);
}
goHome(3)
// 函数可以先声明变量
type myFubc = (money: number) => void
const eat: myFubc = (money) => {
    console.log(`今天午饭吃了${money}块钱`);
}
eat(18.6)
// 函数的可选参数
const handelFun = (firstNmae: string, secondName?: string): string => {
    if (secondName) {
        return firstNmae + secondName
    } else {
        return firstNmae
    }
}
handelFun("杨")
// 默认参数
// 剩余参数
const handelFun2 = function (str1: string, str2: string, str3: string, ...arg: string[]): void {
    console.log(str1);
    console.log(str2);
    console.log(str3);
    console.log(arg);
}

/**
 * 接口 
 * interface 是对象的模板，可以看作是一种类型约定，中文译为“接口”。使用了某个模板的对象，就拥有了指定的类型结构。
 */
interface Person {
    firstName: string;
    lastName: string;
    age: number;
}
let xiaoming: Person = {
    firstName: "王",
    lastName: "小明",
    age: 12
}
// 方括号运算符可以取出 interface 某个属性的类型。
type f = Person['firstName'] // string 

interface Foo {
    x: number,
    y: number,
    z?: string, // 可选属性
    readonly s: boolean, // 只读属性
    say: () => string, // 函数
    m: number | string | Array<boolean>, // 联合类型
}

// 对象的属性索引 任意属性 一个接口中只能定义一个任意属性。如果接口中有多个类型的属性，则可以在任意属性中使用联合类型。
interface A {
    [propNmae: string]: any
}

// 接口继承。
interface Shape {
    name: string;
}

interface Style {
    color: string;
}

interface Circle extends Shape, Style {
    radius: number;
}
// interface 允许多重继承

// 多重接口继承，实际上相当于多个父接口的合并。

// 如果子接口与父接口存在同名属性，那么子接口的属性会覆盖父接口的属性。注意，子接口与父接口的同名属性必须是类型兼容的，不能有冲突，否则会报错。

type Country = {
    name: string;
    capital: string;
}

interface CountryWithPop extends Country {
    population: number;
}

// interface 同样可以继承type命令定义的对象类型。 但是，如果type命令定义的类型不是对象，interface 就无法继承 
// 多个同名接口会合并成一个接口。 同名接口合并时，同一个属性如果有多个类型声明，彼此不能有类型冲突。

/**
 * 泛型
 */
 // 当我们定义一个变量不确定类型的时候有两种解决方式：
 // 1 any 使用any定义时存在的问题：虽然 以 知道传入值的类型但是无法获取函数返回值的类型；另外也失去了ts类型保护的优势
 // 2 泛型 泛型指的是在定义函数/接口/类型时，不预先指定具体的类型，而是在使用的时候在指定类型限制的一种特性。
// 在函数中使用
function test<T>(par:T):T{
    console.log(par);
    return par
}
// test(111) // 类型T为 number
// test("测试") // 类型T为 string
// test<string|boolean>(true) // 也可以自己传入类型 不传类型ts会自己推断T的类型

// 在接口中使用泛型
interface Search {
    <T,Y>(name:T,age:Y):T
}
let fn:Search = function <T, Y>(name: T, id:Y):T {
  console.log(name, id)
  return name;
}
fn('li',11);//编译器会自动识别传入的参数，将传入的参数的类型认为是泛型指定的类型

// 在类中使用
class Animal<T> {
 name:T;
 constructor(name: T){
  this.name = name;
 }
 action<T>(say:T) {
   console.log(say)
 }
}
let cat = new Animal('cat');
cat.action('mimi')

// 泛型约束 很多类型参数并不是无限制的，对于传入的类型存在约束条件。

// 泛型工具类型


</script>

<style lang="scss" scoped>
.content {
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}
</style>